package com.simple.slider.puzzle;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;


public class PuzzleView extends View {
	private static final String LOG_TAG = PuzzleView.class.getName();
	// scale the board to not go entirely to the edge
	private final float shrinkFactor = (float) 0.95; 
	
	private int puzzleDimension;
	
	private SliderTile emptyTile;
	private SliderTile selectedTile;
	
	private PuzzleBoard board;
	private int boardImageID;
	private Bitmap boardImage;
	
	//how far pointer was moved, i.e. distance tile was dragged
	private float dragDistanceX;
	private float dragDistanceY;
	
	// pointer position on the screen
	private float pointerX;
	private float pointerY;
	
	private float tileWidth;
	private float tileHeight;

	private Paint painter;

	private int whiteBorderColor;
	private boolean setCompleteImage;
	private int numRuns = 0;
	
	
    public PuzzleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        setFocusable(true);
        whiteBorderColor = getResources().getColor(R.color.white_border_color);
        
        painter = new Paint();
    }
    
    public void startNewGame(int boardDim, SliderTile[] boardState, int boardImgID) {
		puzzleDimension = boardDim;
		board = new PuzzleBoard(boardDim, boardState);
		boardImageID = boardImgID;
		setCompleteImage = false;
		board.setNumDirections(4);
		
        selectedTile = new SliderTile(-1, -1, -1); //nothing selected to start        
        
        board.shuffleBoard();
        
        emptyTile = board.getBlankTile();
	}
    
    @Override 
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //Log.i(LOG_TAG, "onMeasure");
        
        int screenWidth = getMeasuredWidth();
        int screenHeight = getMeasuredHeight();
        
        Log.i(LOG_TAG, "onMeasure: screenWidth= " + screenWidth + "    screenHeight= " + screenHeight);
        if (screenWidth <= 0 || screenHeight <= 0) {
        	return;
        }

        if (boardImage == null || boardImage.isRecycled()) {
        	
        	boardImage = BitmapFactory.decodeResource(getContext().getResources(), boardImageID);
        	//Log.i(LOG_TAG, "image size: w= " + boardImage.getWidth() + "    h= " + boardImage.getHeight());
        	
        	int bitmapHeight = boardImage.getHeight();
            int bitmapWidth = boardImage.getWidth();
            
            if (bitmapWidth <= 0 || bitmapHeight <= 0) {
            	Log.e(LOG_TAG, "Invalid bitmap image");
            	return;
            }
                        
            // check whether screenWidth or screenHeight is the limiting dimension
            // and adjust the background image accordingly
            if (screenWidth < screenHeight && screenWidth <= bitmapWidth) {
            	float scaleFactor = (float) screenWidth/bitmapWidth;
            	Log.i(LOG_TAG, "scaleFactor: " + scaleFactor);
            	bitmapWidth = (int) (bitmapWidth * scaleFactor * shrinkFactor);
            	bitmapHeight = (int) (bitmapHeight * scaleFactor * shrinkFactor);
            } else if (screenHeight < screenWidth && screenHeight <= bitmapHeight) {
            	float scaleFactor = (float) screenHeight/bitmapHeight;
            	Log.i(LOG_TAG, "scaleFactor: " + scaleFactor);
            	bitmapWidth = (int) (bitmapWidth * scaleFactor * shrinkFactor);
            	bitmapHeight = (int) (bitmapHeight * scaleFactor * shrinkFactor);
            }
            Log.i(LOG_TAG, "Scaled mBitmap size: w= " + bitmapWidth + "    h= " + bitmapHeight);
            
            // use filtering to resize image
            boardImage = Bitmap.createScaledBitmap(boardImage, bitmapWidth, bitmapHeight, true);
        	
            
    	}
        // +1 is to accommodate the tile outlines
        setMeasuredDimension(boardImage.getWidth() + 1, boardImage.getHeight() + 1);

    }
			
    @Override
    protected void onDraw(Canvas canvas) {
    	
    	// Log.i(LOG_TAG, " onDraw");
    	
    	tileWidth = getWidth() / puzzleDimension;
		tileHeight = getHeight() / puzzleDimension;
		
		if (setCompleteImage) {
			Rect entireImage = new Rect(1, 1, (int) tileWidth * puzzleDimension,  (int) tileHeight * puzzleDimension);
        	canvas.drawBitmap(boardImage, entireImage, entireImage, painter);
        	
		} else if (setCompleteImage == false) {
						
	    	for (int currRow = 0; currRow < puzzleDimension; currRow++) {
	    		for (int currCol = 0; currCol < puzzleDimension; currCol++) {
					// skip if this is empty tile
					if (board.getTile(currRow, currCol).num() == -1) {
						continue;
					}
				
					int tileXCoord = (int) tileWidth * currCol;
					int tileYCoord = (int) tileHeight * currRow;
					
					if(board.getTileLoc(selectedTile) > -1) {
						
						int earlierTileCol = 0; int earlierTileRow = 0; 
						int laterTileCol = 0; int laterTileRow = 0;
						
						if (board.getTileLoc(selectedTile) < board.getTileLoc(emptyTile)) {
							earlierTileCol = selectedTile.col();
							earlierTileRow = selectedTile.row();
							laterTileCol = emptyTile.col();
							laterTileRow = emptyTile.row();
						} else {
							earlierTileCol = emptyTile.col();
							earlierTileRow = emptyTile.row();
							laterTileCol = selectedTile.col();
							laterTileRow = selectedTile.row();	
						}
						
	//					Log.i(LOG_TAG, "currRow,currCol: " + currRow + "," + currCol + "   minX,minY: " + earlierTileCol + "," + earlierTileRow + "  maxX,maxY: " + laterTileCol + "," + laterTileRow);
	//					Log.i(LOG_TAG, "tileXCoord,tileYCoord: " + tileXCoord + "," + tileYCoord + "   mOffsetX,mOffsetY: " + dragDistanceX + "," + dragDistanceY);
						
						if (currRow >= earlierTileRow && currRow <= laterTileRow && currCol == earlierTileCol) {
							tileYCoord += dragDistanceY;
						}
						if (currCol >= earlierTileCol && currCol <= laterTileCol && currRow == earlierTileRow) {
							tileXCoord += dragDistanceX;
						}
					}
					
	                int tileNumber = board.getTile(currRow, currCol).num();
	                
	                // upper left coord of each tile, even after tiles have been moved around,
	                // tileNumber allows us to reference back to original tile location to
	                // get its proper image
	                int xSrc = (int)((tileNumber % puzzleDimension) * tileWidth);
	                int ySrc = (int)((tileNumber / puzzleDimension) * tileHeight);
	                
	                // transfer image tiles from bitmap to View
	                Rect imageSource = new Rect(xSrc, ySrc, xSrc + (int) tileWidth,  ySrc + (int) tileHeight);
	                Rect imageDest = new Rect(tileXCoord, tileYCoord, tileXCoord + (int) tileWidth, tileYCoord + (int) tileHeight);
	                 
	                canvas.drawBitmap(boardImage, imageSource, imageDest, painter);
	
		            
		            // tile number labels
	//	            painter.setColor(whiteBorderColor);
	//	            painter.setTextSize(25);
	//	            canvas.drawText(String.valueOf(currRow * puzzleDimension + currCol), tileXCoord + (tileWidth/2), tileYCoord + (tileHeight/2), painter);
	
		            // draw the tile border lines
	                float x2 = tileXCoord + tileWidth;
	                float y2 = tileYCoord + tileHeight;
	                
	                float borderLineCoords[] = {tileXCoord, tileYCoord, x2, tileYCoord, x2, tileYCoord, x2, y2,
	                							x2, y2, tileXCoord, y2, tileXCoord, y2, tileXCoord, tileYCoord};
	                
	                painter.setColor(whiteBorderColor);
	                canvas.drawLines(borderLineCoords, painter);
	
	    		}
	        }
		}
    }
    
    public void setCompleteImage() {
    	Log.i(LOG_TAG, "setCompleteImage");
    	setCompleteImage = true;
    	numRuns++;
    	invalidate();
    }
    
    
    // returns the row and col location of selected tile
    private int[] getRowColOfTile(float x, float y) {
		
		int puzzleViewCoord[] = new int[2];
		
		// PuzzleBoard view is usually not located at 0,0
		// for example, pulldown notification bar on phones takes up space
		getLocationOnScreen(puzzleViewCoord);
		
		Log.i(LOG_TAG, "LocationOnScreen " + puzzleViewCoord[0] + "," + puzzleViewCoord[1]);
		
		if (x - puzzleViewCoord[0] <= this.getWidth() && y - puzzleViewCoord[1] <= this.getHeight()) {
			// convert screen coordinates to row and col number of tile
			int rowNum = (int)((y - puzzleViewCoord[1]) / tileHeight);
	        int colNum = (int)((x - puzzleViewCoord[0]) / tileWidth);

	        Log.i(LOG_TAG, "getRowColOfTile: View w,h= " + this.getWidth() + "," + this.getHeight() + "  r,c= " + rowNum + "," + colNum);
	        
			return new int[] {rowNum, colNum};
		} else {
			return new int[] {-1, -1};
		}
	
    }
        
    private void redrawRow() {
        int tileY = (int)tileHeight * (emptyTile.row());
        invalidate(0, tileY - 1, getRight(), tileY + (int)tileHeight + 1);
    }
    
    private void redrawColumn() {
        int tileX =  (int)tileWidth * (emptyTile.col());
        invalidate(tileX - 1, 0, tileX + (int)tileWidth + 1, getBottom());
    }
        
    public void chooseTile(float x, float y) {        
    	int[] chosenTileLoc = getRowColOfTile(x, y);
    	
    	if (board.isTileMoveable(chosenTileLoc)) { 
    		selectedTile.setRC(chosenTileLoc[0], chosenTileLoc[1]);
    	} else { 
    		selectedTile.setRC(-1, -1);
    	}
        
        pointerX = x;
        pointerY = y;
        dragDistanceX = 0;
        dragDistanceY = 0;
        
        
        Log.i(LOG_TAG, "chooseTile: " + selectedTile.row() + "," + selectedTile.col() + " at " + x + ", " + y);

    }
    
	// simulate clicking by calling the drag and drop methods
	// with predetermined destination
    public void clickTile (float x, float y) {
    	if (board.getTileLoc(selectedTile) >= 0) {
	    	//Log.i(LOG_TAG, " -------- CLICKTILE(): selectedTileIndex: " + selectedTile + "     emptyTileIndex: " + emptyTile + "     x,y: " + x + ", " + y);
	    	if (selectedTile.col() == emptyTile.col()) {
	    		// move the col 
	    		if (board.getTileLoc(emptyTile) < board.getTileLoc(selectedTile)) {
	    			dragTile(x, y - tileHeight);
	    			dropTile(x, y - tileHeight);
	    		} else {
	    			dragTile(x, y + tileHeight);
	    			dropTile(x, y + tileHeight);
	    		}
	    	} else if (selectedTile.row() == emptyTile.row()) {
	    		// move the row
	    		if (board.getTileLoc(emptyTile) < board.getTileLoc(selectedTile)) {
	    			dragTile(x - tileWidth, y);
	    			dropTile(x - tileWidth, y);
	    		} else {
	    			dragTile(x + tileWidth, y);
	    			dropTile(x + tileWidth, y);
	    		}
	    	}
    	}
    }
    
    public boolean dropTile(float x, float y) {
        
        // Log.i(LOG_TAG, "Drop: " + selectedTile.row() + "," + selectedTile.col() + " at (" + x + ", " + y + ")");

    	if (board.getTileLoc(selectedTile) >= 0 && (Math.abs(dragDistanceX) > tileWidth/2 ||
    			Math.abs(dragDistanceY) > tileHeight/2)) {   
    		int changedDim = board.updateTilesPlacement(selectedTile);
    		
    		if (changedDim == PuzzleBoard.ROW_CHANGED) {
    			redrawRow();
    		} else if (changedDim == PuzzleBoard.COL_CHANGED) {
    			redrawColumn();
    		}
        	selectedTile.setRC(-1, -1); //clear out selection
        	return true;
    	} else if (selectedTile.col() == emptyTile.col()) {
            redrawColumn();
        } else if (selectedTile.row() == emptyTile.row()) {
            redrawRow();
        }
    	selectedTile.setRC(-1, -1);
    	return false;
    }
    
    public void dragTile(float x, float y) {
        if (board.getTileLoc(selectedTile) < 0) {
    	    return;
        }
                
        // Log.i(LOG_TAG, " -------- dragTile(): selectedTileIndex: " + selectedTile + "     mEmptyIndex: " + emptyTile + "     x,y: " + x + ", " + y);
                
        // Drag only vertically or horizontally
        if (selectedTile.col() == emptyTile.col()) {
        	// Log.i(LOG_TAG, " -------- " + selectedTile + "%" + puzzleDimension + " == " + emptyTile + "%" + puzzleDimension);
	    	if (board.getTileLoc(selectedTile) > board.getTileLoc(emptyTile)) {
	    		dragDistanceY += y - pointerY;
	            if (dragDistanceY > 0) {
	            	dragDistanceY = 0;
	            } else if (Math.abs(dragDistanceY) > tileHeight) {
	            	dragDistanceY = -tileHeight;
	            }
	            pointerY = y;

	    	} else {
	            dragDistanceY += y - pointerY;
	            if (dragDistanceY < 0) {
	            	dragDistanceY = 0;
	            } else if (dragDistanceY > tileHeight) {
	            	dragDistanceY = tileHeight;
	            }
	            pointerY = y;
	    	}
	    	redrawColumn();
    	} else if (selectedTile.row() == emptyTile.row()) {
    		// Log.i(LOG_TAG, " -------- " + selectedTile + "/" + puzzleDimension + " == " + emptyTile + "/" +  puzzleDimension);
    		if (board.getTileLoc(selectedTile) > board.getTileLoc(emptyTile)) {
	            dragDistanceX += x - pointerX;
	            if (dragDistanceX > 0) {
	            	dragDistanceX = 0;
	            } else if (Math.abs(dragDistanceX) > tileWidth) {
	            	dragDistanceX = -tileWidth;
	            }
	            pointerX = x;
	    	} else {
	            dragDistanceX += x - pointerX;
	            if (dragDistanceX < 0) {
	            	dragDistanceX = 0;
	            } else if (dragDistanceX > tileWidth) {
	            	dragDistanceX = tileWidth;
	            }
	            pointerX = x;
	    	}
	    	redrawRow();
    	}        
    }
    
    public PuzzleBoard getPuzzleBoard() {
    	return board;
    }
}
